home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Info-Mac 4
/
Info_Mac IV CD-ROM (Pacific HiTech Inc.)(August 1994).iso
/
Development
/
Source
/
Rotation-c
/
ROT-PSIG .c
next >
Wrap
Text File
|
1994-06-03
|
20KB
|
592 lines
//• ------------------------------------------------------------------- •//
//• Another public domain C example source demo, brought back from •//
//• the dead at: itty bitty bytes™ - by Kenneth A. Long! •//
//• Made to run in Think C™ on 2 June 1994. •//
//• Uses no resource file - just add MacTraps and ANSI libraries. •//
//• The original of this file was found on ftp.cso.uiuc.edu in mac/MUG. •//
//• ------------------------------------------------------------------- •//
//• 3D Demonstration (DRAFT)
//• as told in Megamax C V2.1
//• by
//• M H Miller ... 30 March 1986.
//• wire frame 10 April 1986.
//• rotations 15 April 1986.
//• visibility 1 May 1986.
//• touchup 3 May 1986.
//• add 1st order lighting 8 May 1986.
//• This is an 'application' which eventually will take its proper
//• place among a set of exercises whose purpose is enhancing
//• understanding of C programming (Megamax C is the particular vehicle
//• used), and in facilitating oozing through Inside Macintosh. In
//• most of these exercises the methodology is to explore a number
//• of Inside Macintosh functions and procedures, observe their visual
//• effects (if any), study their syntax, and in general evaluate their
//• use and usfulness in the Macintosh interface. Modifications,
//• enhancements, non_commercial uses of this material, and tactful
//• comments are invited, indeed encouraged.
//• In studying the exercise (s) keep in mind (for my sake at least)
//• that the principal intention is not to optimize the operation (s)
//• performed but rather to examine Macintosh operations and C
//• programming. With that view what you don't care for should be
//• considered an objective illustration of what not to do.
//• --------------------------------------------------------
//• This exercise involves computation of the isometric projection of a
//• solid and rotation of that solid about an axis. The character of
//• the solid is constrained so that particular circumstances requiring
//• extended computation and manipulation are avoided. Specifically the
//• solid must have plane faces which are closed polygons. The solid
//• must be 'convex', i.e., the angle between outward normals to two
//• contiguous faces must be greater than 90 degrees; this eliminates
//• solids with 'holes', indentations, or 'pimples'. For a convex
//• solid a face either is visible in its entirety or it isn't; there
//• is no 'shadowing' of one face by others. Rotations of the solid
//• are about a single (coordinate) axis at a time for illustrative
//• simplicity, although generalizing is not all that difficult or
//• involved. For each rotated position an equiangular isometric
//• projection is computed. In addition the orientation of each face
//• of a projection is computed to determine if the outward normal of
//• a projected face points into or out of the screen; a face is visible
//• looking into the screen if its outward normal points out of the
//• screen. A dynamic presentation of the rotating solid is drawn,
//• optionally either as a wire frame or as a solid. Additional
//• variations are planned for addition now and then.
//• NOTE 1: The nature of the data structures used is of some importance.
//• However no great emphasis was placed on optimizing these in this
//• illustration. Consider this illustration to be an illustration and
//• not a 'how to' prescription. Nevertheless the program is written to
//• allow easy redefinition of the object rotated, within the simplifying
//• constraints noted.
//• NOTE 2: This is a 'brute force' version to provide a reference against
//• which to evaluate the efficacy of measures to reduce flickering and
//• image discontinuity. Some modifications to be added sooner or later
//• are bitmap switching, assembly language interventions, synchronizing
//• to the blanking interrupts, and cleaning the screen as a VBL task.
//• --------------------------------------------------------.
#include <stdio.h>
#include <math.h>
//typedef int void; //• Thoroughly Modern Miller.
#define X 0 //• Axis references.
#define Y 1
#define Z 2
//• For simplicity the object to be drawn is predefined. The
//• specification is for the 'base' position from which all rotated
//• positions are computed. Within the constraints on the type of
//• object allowed a new object can be redefined by redefining the
//• parameters asdescribed or readily inferred. The following three
//• parameters are used to set computation loop limits; redefine to suit.
#define NMBR_FACES 9
#define NMBR_VERTICES 9
#define NMBR_SETS 20 //• rotation angle step == 360 / NMBR_SETS.
//• Object vertex coordinates (relative to vertex 0). This example is
//• for a regulartrapazoidal parallelpiped. This part of the
//• specification only partially defines thesolid; still to come is
//• specification of the face edges, i.e., the connections between
//• vertices which define the individual faces. Dimensions are
//• referenced to LX, LY, and LZ. Note that rotations will be about
//• coordinate axes for simplicity. However that does not constrain
//• the initial orientation of the object; it can be 'tilted' as desired.
#define LX 80
#define LY 40
#define LZ 60
//• #####################################
//• (Try using this vertex table, and comment out the other one - K.A.L.)
//• 'Plain Jane' initial orientation (not used)
/*
int vertex [NMBR_VERTICES] [3] =
{
{ 0, 0, 0 },
{ 0, 0, LZ },
{ LX, 0, LZ },
{ LX, 0, 0 },
{ 3 * LX / 4, LY, LZ / 4 },
{ 3 * LX / 4, LY, 3 * LZ / 4 },
{ LX / 4, LY, 3 * LZ / 4 },
{ LX / 4, LY, LZ / 4 },
{ LX / 2, -LY / 2, LZ / 2 }
};
*/
//• ####################################
//• 'Centerfold' (more exposed) initial position;
//• scale not the same as for Plain Jane.
//• This is the specification used for the program.
int vertex [NMBR_VERTICES] [3] =
{
{ 0, 0, LZ / 2 },
{ LX / 2, 0, LZ },
{ LX, 0, LZ / 2 },
{ LX / 2, 0, 0 },
{ LX / 2, LY, LZ / 4 },
{ 3 * LX / 4, LY, LZ / 2 },
{ LX / 2, LY, 3 * LZ / 4},
{ LX / 4, LY, LZ / 2 },
{ LX / 2, -LY / 2, LZ / 2 }
};
//• A face is defined its edges; in turn these are defined by a set
//• of vertices. The vertex list is determined as follows:
//• a) Imagine the outward normal to the surface to be drawn.
//• b) Imagine your right hand curled around the normal, thumb
//• pointing outward from theface along the normal.
//• c) Follow the curl of your fingers in listing the vertices
//• forming the face edges. Note that the last vertex listed is
//• the same as the first one; any of the face vertices maybe 'first'
//• in the list.
//• The number of vertices need not be the same for all faces. Note
//• again that faces are required to be planar polygons. Violation
//• of this constraint generally shows up as overlapping faces as
//• the object rotates.The following 'face' specification is for the
//• trapazoidal pyramid. Per standard C rules any array elements not
//• specified will be set to 0, although this is not important since
//• they are not used at all.
int face [NMBR_FACES] [5] =
{
{0, 1, 6, 7, 0},
{1, 2, 5, 6, 1},
{2, 3, 4, 5, 2},
{7, 4, 3, 0, 7},
{7, 6, 5, 4, 7},
{0, 3, 8, 0},
{8, 1, 0, 8},
{8, 2, 1, 8},
{8, 3, 2, 8}
};
//• Define the coordinates xr, yr of the center of rotation/origin (this
//• will be taken to be somewhere in the plane of the screen), and the
//• offset of vertex 0 from the center of rotation (dxo, dyo, dzo).
//• Thus the absolute location of, say the X-coordinate of vertex 4, is
//• xr + dxo + vertex [4] [X]. See below for the computation of
//• projection coordinates.
int xr = 200, yr = 160, zr = 0, dxo = 50, dyo = 30, dzo = -50;
//• For each face a polygon will be computed (later) for the
//• projection of that face.
PolyHandle face_poly [NMBR_SETS] [NMBR_FACES];
//• For each face, and in each set the direction of the normal to a
//• projected face is computed to determine if that face is visible
//• or not. In addition the illumination (yes or no) of a face from
//• a source to the left and up is determined.
short visible [NMBR_SETS] [NMBR_FACES];
short light [NMBR_SETS] [NMBR_FACES];
short drawflag; //• Switch between drawing types. SOLID illustrates
//• the use of the 'visibility'computation, i.e., only
//• 'visible' faces are drawn. SOLID_LIGHT adds
//• first-order illumination.
#define SOLID_LIGHT 0
#define SOLID 1
#define WIRE_FRAME 2
Rect option_rect, display_rect; //• Housekeeping.
WindowPtr newWindow; //• Added by K.A.L.
Rect dragRect; //• Added by K.A.L.
Rect windowBounds = { 20, 0, 384, 512}; //• Added by K.A.L.
//• Prot's:
int main (void);
void Compute_Rotation_Data (int axis);
void Rotate_Object (void);
main ()
{
EventRecord the_event;
Point mousepoint;
GrafPtr the_port;
int i, j;
register int this_set, next_set;
Rect temp_rect;
InitGraf (&thePort);
InitFonts ();
InitWindows ();
InitCursor ();
//• The next 3 lines was added by K.A.L. because...
dragRect = qd.screenBits.bounds;
newWindow = NewWindow (0L, &windowBounds, "\p", true,
noGrowDocProc, (WindowPtr) -1L, true, 0);
SetPort (newWindow);
//• ...the next 6 lines messed things up.
//• Use the window manager port (desktop) directly.
//• Open up the clipregion to include themenu bar and clear the screen.
//• Define a housekeeping rectangle for erasing the screen.
// GetWMgrPort (&the_port);
// SetPort (the_port);
// ClipRect (& (the_port->portRect));
// EraseRect (& (the_port->portRect));
// SetOrigin (0, 0);
//• K.A.L. made the following be windowBounds (above).
// SetRect (&display_rect, 0, 21, 512, 342);
//• Startup with SOLID_LIGHT, X-axis rotation.
Compute_Rotation_Data (X);
drawflag = SOLID_LIGHT;
for (;;)
{
GetNextEvent (everyEvent, &the_event);
if (the_event.what == mouseDown )
{
GetMouse (&mousepoint);
if (PtInRect (mousepoint, &option_rect) )
{
switch (i = mousepoint.h / 15)
{
case 0: //• Q for QUIT.
exit (0);
break;
case 1:
case 2:
case 3: //• Rotate X, Y, Z respectively.
Compute_Rotation_Data (i-1);
break;
case 4: //• SOLID, SOLID_LIGHT, WIRE_FRAME.
if (++drawflag == 3) drawflag = 0;
break;
}
}
else
while (Button ()) ; //• Freeze frame.
}
Rotate_Object (); //• Take a step.
}
}
//• The isometric projection is an equiangular proection on the
//• plane (screen). The y-axis coordinate of a point is drawn
//• vertically (+ down) from the origin. The x-axis position is
//• along a Line drawn 30 degrees down from horizontal, while the
//• z-axis is drawn along a Line elevated 30 degrees above the
//• horizontal. The net result is that the screen position relative
//• to the origin of the point x, y, z is (in screen position
//• coordinates) (x, y) = (x + z) * cos (30), y + (x - z) * sin (30)
//• This procedure computes the rotated position of the vertices,
//• computes whether a projected face is 'visible' or not, and
//• defines polygons for each face in each projection. The data
//• produced is 'face_poly [this_set] [i]' which is the polygon
//• for drawing face #i after a rotation of 360 * this_set / NMBR_SETS.
//• No indication provided for axis of rotation. In addition
//• 'visible [this_set] [i]' =1/0 indicates that face is visible / invisible
//• seen looking into the screen.
//• Note: The only global parameters provided by this illustrative
//• computation are the polygon handles and the 'visibility' and
//• 'light' arrays.
void Compute_Rotation_Data (int axis)
{
register int this_set, i, j; //• Rotation angle is 360 * this_set / NMBR_SETS.
int basev [NMBR_VERTICES] [3]; //• Vertex coordinates offset from center of rotation.
int v [NMBR_VERTICES] [3]; //• Vertex rotated coordinates.
int p_vx [NMBR_VERTICES], p_vy [NMBR_VERTICES]; //• Projection coordinates.
int ax, ay, az, bx, by, bz; //• Vector components for computing direction cosines.
//• Miscellaneous utility variables•//
Rect count_rect;
int v0, v1, v2;
float sin_table, cos_table;
#define D_THETA 6.283185 / NMBR_SETS
//• Cf Psych 100 notes.
SetRect (&count_rect, 0, 21, 512, 342);
EraseRect (&count_rect);
TextSize (18);
TextFace (bold);
TextFont (geneva);
PenNormal ();
MoveTo (200, 150);
DrawString ("\pHOLD ON");
MoveTo (50, 170);
DrawString ("\pI'M COMPUTING WHAT I NEED TO KNOW");
//• Compute the rotated position of the vertices. Note that the
//• computation references the base vertex definitions for each
//• computation rather than accumlating incremental rotations.
//• For real time rotation computations the latter would be
//• appropriate, provided regular reinitialization, direct or
//• indirect, avoided error accumulation.
//• Locate vertices relative to center of rotation.
for (i = 0; i < NMBR_VERTICES; ++i)
{
basev [i] [X] = vertex [i] [X] + dxo;
basev [i] [Y] = vertex [i] [Y] + dyo;
basev [i] [Z] = vertex [i] [Z] + dzo;
}
//• Angular increment is 360 / NMBR_SETS.
for (this_set = 0; this_set < NMBR_SETS; ++this_set)
{ //• Convenience variables.
sin_table = sin (D_THETA * this_set);
cos_table = cos (D_THETA * this_set);
//• Rotate the vertices. Note that the coordinates are relative to the center of rotation.•//
for (i=0;i<NMBR_VERTICES;++i)
{
switch (axis)
{ case Z:
v [i] [X] = basev [i] [X] *
cos_table - basev [i] [Y] *
sin_table;
v [i] [Y] = basev [i] [X] *
sin_table + basev [i] [Y] *
cos_table;
v [i] [Z] = basev [i] [Z];
break;
case Y:
v [i] [X] = basev [i] [X] *
cos_table + basev [i] [Z] * sin_table;
v [i] [Y] = basev [i] [Y];
v [i] [Z] = -basev [i] [X] *
sin_table + basev [i] [Z] * cos_table;
break;
case X:
v [i] [X] = basev [i] [X];
v [i] [Y] = basev [i] [Y] *
sin_table + basev [i] [Z] * cos_table;
v [i] [Z] = -basev [i] [Y] *
cos_table + basev [i] [Z] * sin_table;
break;
}
}
//• A source of light is assumed far away and to the right. A
//• face is illuminated if 'sees'the light, i.e., if its outward
//• normal is directed to the right. In addition of course
//• the face will have to be visible in the projection, as
//• computed later. This illumination algorithm does not
//• provided graded lighting as would occur in reality. A
//• face either is or is not illuminated.
//• Note: See comments below for 'visible' computation for
//• additional pertinent remarks not included here.
for (i = 0; i < NMBR_FACES; ++i)
{
v0 = face [i] [0];
v1 = face [i] [1];
v2 = face [i] [2];
ay = v [v2] [Y] - v [v1] [Y];
by = v [v0] [Y] - v [v1] [Y];
az = v [v2] [Z] - v [v1] [Z];
bz = v [v0] [Z] - v [v1] [Z];
//• An illuminated face has a 0 flag..
light [this_set] [i] = (ay * bz - az * by) > 0 ?0 :1;
}
//• Compute the isometric positions for each vertex in the
//• current set. The projection is computed relative to the
//• center of rotation as origin and then reset relative to
//• the screen origin.
for (i = 0; i < NMBR_VERTICES; ++i)
{
p_vx [i] = xr + .86603 * (v [i] [X] + v [i] [Z]);
p_vy [i] = yr + v [i] [Y] + (v [i] [X] - v [i] [Z])/2;
}
//• A face of the projection is 'visible' if it can be seen
//• looking into the screen. To determine visibility use the
//• fact that faces are defined by the array face [] [4] which
//• provides the clockwise sequence of vertices defining the
//• face. The procedure is:
//• a) Select three consecutive
//• vertices #0, #1, #2 from the face definition vector to
//• define two projected face edges. The first three are used.
//• Note that the face definition vector lists vertices
//• circulating CCW about outward directed normal.
//• b) Define edge#1 as fron vertex #1 to vertex #2; call this
//• edge A.
//• c) Define edge#2 as fron vertex #1 to vertex #0; call this
//• edge B.
//• d) The normal is A x B = Ax * By - Ay * Bx;
//• if this is negative (out of the screen) the face is visible.
//• Note: Notational guide: The first three vertices of face #
//• this_set are face [this_set] [0], face [this_set] [1], and
//• face [this_set] [2]. Compute components as:
//• Ax = v [face [this_set] [2]] [X] -
//• v [face [this_set] [1]] [X] ;
//• Bx = v [face [this_set] [0]] [X] -
//• v [face [this_set] [1]] [X] ;
//• The fact that the vertex coordinates are relative to the
//• center of rotation washes out because of the difference is
//• taken.
for (i = 0; i < NMBR_FACES; ++i)
{
v0 = face [i] [0];
v1 = face [i] [1];
v2 = face [i] [2];
ax = p_vx [v2] - p_vx [v1];
bx = p_vx [v0] - p_vx [v1];
ay = p_vy [v2] - p_vy [v1];
by = p_vy [v0] - p_vy [v1];
//• Condition a flag 'visible [this_set] [i]' to indicate
//• visible faces..
visible [this_set] [i] = (ax * by - ay * bx) < 0 ?1 :0;
}
//• Compute the face polygons. Note that restriction to
//• convex solids means a face either is visible or it is not;
//• no partial visibility because of shadowing.
for (i = 0; i < NMBR_FACES; ++i)
{
face_poly [this_set] [i] = OpenPoly ();
MoveTo (p_vx [face [i] [0]], p_vy [face [i] [0]] );
for (j = 1; j < NMBR_VERTICES; ++j)
{
LineTo (p_vx [face [i] [j]], p_vy [face [i] [j]] );
if (face [i] [j] == face [i] [0] )
break;
}
ClosePoly ();
}
//• Display computational progress as a countdown.
SetRect (&count_rect, 200, 175, 250, 205);
EraseRect (&count_rect);
MoveTo (210, 200);
i = (NMBR_SETS-this_set);
if (i > 99)
DrawChar (48 + i / 100);
else
DrawChar (' ');
if (i > 99)
{
i %= 100;
DrawChar (48 + i / 10);
i %= 10 ;
}
else
{
if (i>9)
{
DrawChar (48 + i / 10);
i %= 10;
}
else
DrawChar (' ');
}
DrawChar (48+i);
}
//• Draw 'option bar'.
TextSize (12);
SetRect (&option_rect, 0, 0, 76, 20);
FrameRect (&option_rect);
MoveTo (5, 16);
DrawChar ('Q');
MoveTo (0, 15);
Line (0, 15);
MoveTo (20, 16);
DrawChar ('X');
MoveTo (0, 30);
Line (0, 15);
MoveTo (35, 16);
DrawChar ('Y');
MoveTo (0, 45);
Line (0, 15);
MoveTo (50, 16);
DrawChar ('Z');
MoveTo (0, 60);
Line (0, 15);
MoveTo (65, 16);
DrawChar ('F');
}
void Rotate_Object ()
{
register int i;
static int set = 0;
register PolyHandle *poly;
poly = &face_poly [set] [0];
EraseRect (&display_rect);
if (drawflag == SOLID_LIGHT)
{
for (i=0;i<NMBR_FACES;++i)
{
if (visible [set] [i] ==1)
{
if (light [set] [i] ==1)
FillPoly (* (poly + i), <Gray);
FramePoly (* (poly + i));
}
}
}
else
if (drawflag == SOLID)
{
for (i = 0; i < NMBR_FACES; ++i)
if (visible [set] [i] ==1)
FramePoly (* (poly + i));
}
else
for (i = 0; i < NMBR_FACES; ++i)
FramePoly (* (poly + i));
if (++set == NMBR_SETS )
set = 0; //• Update the rotation angle.
}